/*
 * Jenny Ruppen 09-730-391
 * Sebastien Goldberg 08-914-855
 * Henry Raymond 08-928-061
 * Stefan Mussato 09-752-270
 */
 
package ch.uzh.ifi.sopra.gruppe9.game;

import java.util.ArrayList;

import com.jme3.asset.AssetManager;
import com.jme3.collision.CollisionResults;
import com.jme3.input.FlyByCamera;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.shape.Quad;
import com.jme3.shadow.PssmShadowRenderer;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.BufferUtils;


/**
 * Scenery of the in-game screen.
 */
public class Scenery extends Node {
	
	
	/**
	 * Degree to Radian factor.
	 */
	public static final float DEG2RAD = 0.0174533f;
	
	
	/**
	 * Enumerator for the list of positions.
	 */
	public enum Location {BASIC_SCENE_CAMERA, BASIC_SCENE_LOOK_AT, PALACE_OF_THE_CALIPH, BANK, CONSTRUCTION_AUTHORITY, TOWER_ELEMENT_MARKET, HOUSE_OF_SPIES, BUILDING_LOT_2_1, BUILDING_LOT_2_2, BUILDING_LOT_3_1, BUILDING_LOT_3_2, BUILDING_LOT_3_3, BUILDING_LOT_4_1, BUILDING_LOT_4_2, BUILDING_LOT_4_3, BUILDING_LOT_4_4};

	/**
	 * Asset Manager of jME3
	 */
	private static AssetManager assetManager;
	
	
	/**
	 * List of all registered mouseOver event elements of the scenery.
	 */
	private static ArrayList<IMouseOverable> mouseOverables = new ArrayList<IMouseOverable>();
	
	
	/**
	 * List of all registered click event elements of the scenery.
	 */
	private static ArrayList<IClickable> clickables = new ArrayList<IClickable>();
	
	
	/**
	 * Parent node of all clickable elements in 3D space.
	 */
	private static Node clickables3D;
	
	
	/**
	 * Geometry node of all content on the scenery.
	 */
	private static Node scenery;
	
	
	/**
	 * Parent nodes of all towers of all players.
	 */
	private static Node[] towerNodes = new Node[4];
	
	
	/**
	 * Terrain of the scenery.
	 */
	private static TerrainQuad terrain;
	
	
	/**
	 * Camera of the scene.
	 */
	private static Camera cam;
	
	
	/**
	 * Free camera to look at scene from other direction.
	 */
	private static FlyByCamera freeCam;
	
	
	/**
	 * True if free camera is on.
	 */
	private static boolean freeCamOn = false;
	
	
	/**
	 * Current look-at point of the camera.
	 */
	private static Vector3f camLookAt;
	
	
	/**
	 * Camera location at the start of the animation.
	 */
	private static Vector3f camAnimationStartLocation;
	
	
	/**
	 * Camera look-at point at the start of the animation.
	 */
	private static Vector3f camAnimationStartLookAt;

	
	/**
	 * Camera location at the end of the animation.
	 */
	private static Vector3f camAnimationDestinationLocation;
	
	
	/**
	 * Camera look-at point at the end of the animation.
	 */
	private static Vector3f camAnimationDestinationLookAt;
	
	
	/**
	 * Camera animation position in time.
	 */
	private static float camAnimationTimer = -100.0f;
	
	
	/**
	 * Callback function which is called then the animation is over.
	 */
	private static ICallback camAnimationCallback;
	
	
	/**
	 * X coordinates of the building lots.
	 */
	private static final float[][] buildingLotX = {
		{59, 80},
		{59, 69, 80},
		{53, 63, 74, 85}
	};
	
	
	/**
	 * Z coordinates of the building lots.
	 */
	private static final float[][] buildingLotZ = {
		{432, 453},
		{432, 442, 453},
		{427, 437, 447, 458}
	};
	
	/**
	 * Rotation in degrees of the towers in the building lot.
	 * (There are only 16 roof elements -> maximum amount of towers: 16
	 */
	private static final float[] towerRotation = {
		0.0f, -45.0f, 45.0f, -90.0f, 90.0f, -135.0f, 135.0f, -22.5f, 22.5f, -67.5f, 67.5f, -112.5f, 112.5f, -157.5f, 157.5f, 180.0f
	};
	

	/**
	 * Set up scene. Create geometry.
	 */
	public static void setup(AssetManager assetManager) {
		
		Scenery.assetManager = assetManager;
		
		scenery = new Node();
		
		clickables3D = new Node();
		
        Texture heightMapImage = assetManager.loadTexture("assets/Terrain/heightmap.png");
        AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();
     
        terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
        terrain.setLocalScale(1.0f, 0.4f, 1.0f);
        terrain.setLocalTranslation(256.0f, 0.0f, 256.0f);
        
        /**Geometry palaceOfTheCaliph = new Geometry("PalaceOfTheCaliph", new Quad(39.0f, 39.0f));
        palaceOfTheCaliph.setMaterial(MaterialManager.getPalaceOfTheCaliph());
        palaceOfTheCaliph.setLocalTranslation(180.0f - 13.5f, terrain.getHeight(new Vector2f(180.0f, 337.0f)), 337.0f - 13.5f);
        palaceOfTheCaliph.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
        palaceOfTheCaliph.setQueueBucket(Bucket.Transparent);
        palaceOfTheCaliph.updateModelBound();
        clickables3D.attachChild(palaceOfTheCaliph); */
        
        Spatial palaceOfTheCaliph = assetManager.loadModel("assets/Models/PalaceOfTheCaliph/PalaceOfTheCaliph.obj");
        palaceOfTheCaliph.setName("PalaceOfTheCaliph");
        palaceOfTheCaliph.rotate(0.0f, 0.0f, 0.0f);
        Material mat_colors = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_colors.setTexture("ColorMap", assetManager.loadTexture("assets/Models/PalaceOfTheCaliph/PotC.jpg"));
        palaceOfTheCaliph.setMaterial(mat_colors);
        palaceOfTheCaliph.setShadowMode(ShadowMode.CastAndReceive);
        palaceOfTheCaliph.setLocalTranslation(getLocationOf(Location.PALACE_OF_THE_CALIPH));
        palaceOfTheCaliph.scale(5.0f);
        palaceOfTheCaliph.rotate(0.0f, 45.0f * DEG2RAD, 0.0f);
        clickables3D.attachChild(palaceOfTheCaliph);
        
        /**Geometry bank = new Geometry("Bank", new Quad(26.0f, 26.0f));
        bank.setMaterial(MaterialManager.getBank());
        bank.setLocalTranslation(107.0f - 9.0f, terrain.getHeight(new Vector2f(107.0f, 325.0f)), 325.0f - 9.0f);
        bank.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
        bank.setQueueBucket(Bucket.Transparent);
        clickables3D.attachChild(bank); */
        
        Spatial bank = assetManager.loadModel("assets/Models/Bank/Bank.obj");
        bank.setName("Bank");
        bank.rotate(0.0f, 0.0f, 0.0f);
        Material mat_color = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_color.setTexture("ColorMap", assetManager.loadTexture("assets/Models/Bank/Bank.png"));
        bank.setMaterial(mat_color);
        bank.setShadowMode(ShadowMode.CastAndReceive);
        bank.setLocalTranslation(getLocationOf(Location.BANK));
        bank.scale(5.0f);
        bank.rotate(0.0f, -125.0f * DEG2RAD, 0.0f);
        clickables3D.attachChild(bank);
        
        Geometry constructionAuthority = new Geometry("ConstructionAuthority", new Quad(26.0f, 26.0f));
        constructionAuthority.setMaterial(MaterialManager.getConstructionAuthority());
        constructionAuthority.setLocalTranslation(119.0f - 9.0f, terrain.getHeight(new Vector2f(119.0f, 358.0f)), 358.0f - 9.0f);
        constructionAuthority.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
        constructionAuthority.setQueueBucket(Bucket.Transparent);
        clickables3D.attachChild(constructionAuthority);
        
        /**Spatial constructionAuthority = assetManager.loadModel("assets/Models/ConstructionAuthority/ConstructionAuthority.obj");
        constructionAuthority.setName("ConstructionAuthority");
        constructionAuthority.rotate(0.0f, 0.0f, 0.0f);
        Material mat_walls = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_walls.setTexture("ColorMap", assetManager.loadTexture("assets/Models/ConstructionAuthority/ConstructionAuthority.png"));
        constructionAuthority.setMaterial(mat_walls);
        constructionAuthority.setShadowMode(ShadowMode.CastAndReceive);
        constructionAuthority.setLocalTranslation(getLocationOf(Location.CONSTRUCTION_AUTHORITY));
        constructionAuthority.scale(5.0f);
        constructionAuthority.rotate(0.0f, -10.0f * DEG2RAD, 0.0f);
        constructionAuthority.updateModelBound();
        clickables3D.attachChild(constructionAuthority);*/
        
        Geometry towerElementMarket = new Geometry("TowerElementMarket", new Quad(52.0f, 39.0f));
        towerElementMarket.setMaterial(MaterialManager.getTowerElementMarket());
        towerElementMarket.setLocalTranslation(144.0f - 18.0f, terrain.getHeight(new Vector2f(144.0f, 385.0f)), 385.0f - 18.0f);
        towerElementMarket.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
        towerElementMarket.setQueueBucket(Bucket.Transparent);
        towerElementMarket.setShadowMode(ShadowMode.CastAndReceive);
        clickables3D.attachChild(towerElementMarket);
        
       /** Spatial towerElementMarket = assetManager.loadModel("assets/Models/Marketplace/Marketplace2.obj");
        towerElementMarket.setName("TowerElementMarket");
        towerElementMarket.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
        Material mat_wall = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_wall.setTexture("ColorMap", assetManager.loadTexture("assets/Models/Marketplace/Marketplace.jpg"));
        towerElementMarket.setMaterial(mat_wall);
        towerElementMarket.setShadowMode(ShadowMode.CastAndReceive);
        towerElementMarket.setLocalTranslation(getLocationOf(Location.TOWER_ELEMENT_MARKET));
        towerElementMarket.scale(5.0f);
        towerElementMarket.updateModelBound();
        //towerElementMarket.setLocalScale(0.0f, 0.0f, 0.0f);
        towerElementMarket.rotate(0.0f, 0.0f * DEG2RAD, 0.0f);
        clickables3D.attachChild(towerElementMarket);*/
        
        /*Geometry houseOfSpies = new Geometry("HouseOfSpies", new Quad(26.0f, 26.0f));
        houseOfSpies.setMaterial(MaterialManager.getHouseOfSpies());
        houseOfSpies.setLocalTranslation(188.0f - 9.0f, terrain.getHeight(new Vector2f(188.0f, 394.0f)), 394.0f - 9.0f);
        houseOfSpies.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
        GUI.getRootNode().attachChild(houseOfSpies);
        houseOfSpies.setQueueBucket(Bucket.Transparent);*/
        
        Spatial houseOfSpies = assetManager.loadModel("assets/Models/HouseOfSpies/HouseOfSpies2.obj");
        houseOfSpies.setName("HouseOfSpies");
        houseOfSpies.rotate(0.0f, 3.0f, 0.0f);
        Material mat_brick = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_brick.setTexture("ColorMap", assetManager.loadTexture("assets/Models/HouseOfSpies/bild.jpg"));
        houseOfSpies.setMaterial(mat_brick);
        houseOfSpies.setShadowMode(ShadowMode.CastAndReceive);
        houseOfSpies.setLocalTranslation(getLocationOf(Location.HOUSE_OF_SPIES));
        houseOfSpies.scale(5.0f);
        houseOfSpies.rotate(0.0f, 90.0f * DEG2RAD, 0.0f);
        houseOfSpies.updateModelBound();
        clickables3D.attachChild(houseOfSpies);
        
        scenery.attachChild(clickables3D);
        
        Geometry sky = createQuad(2048.0f, 588.0f, MaterialManager.getSky(), 0.0f, 0.0f, 1.0f, 1.0f, true);
        sky.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
        sky.move(1012.0f, 340.0f, -500.0f);
        sky.setQueueBucket(Bucket.Sky);
        sky.setCullHint(Spatial.CullHint.Never);
        scenery.attachChild(sky);
        
        for(int i = 0; i < 4; i++) {
        	towerNodes[i] = new Node();
        	scenery.attachChild(towerNodes[i]);
        }
        
	}


	/**
	 * Creates the terrain corresponding to the number of players
	 */
	public static void createTerrain(int numberOfPlayers) {
		
		Material terrainMaterial = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
        
        // Distribution of the three textures: 
        // Texture 1 (red), Texture 2 (green), Texture 3 (blue)
        terrainMaterial.setTexture("Alpha", assetManager.loadTexture("assets/Terrain/textures" + numberOfPlayers + ".png"));
     
        Texture texture1 = assetManager.loadTexture("assets/Textures/Scenery/grass.png");
        texture1.setWrap(WrapMode.Repeat);
        terrainMaterial.setTexture("Tex1", texture1);
        terrainMaterial.setFloat("Tex1Scale", 32f);
     
        Texture texture2 = assetManager.loadTexture("assets/Textures/Scenery/dirt.png");
        texture2.setWrap(WrapMode.Repeat);
        terrainMaterial.setTexture("Tex2", texture2);
        terrainMaterial.setFloat("Tex2Scale", 32f);
     
        Texture texture3 = assetManager.loadTexture("assets/Textures/Scenery/gravel.png");
        texture3.setWrap(WrapMode.Repeat);
        terrainMaterial.setTexture("Tex3", texture3);
        terrainMaterial.setFloat("Tex3Scale", 32f);

        terrain.setMaterial(terrainMaterial);
        terrain.setShadowMode(ShadowMode.Receive);
        
        scenery.attachChild(terrain);
	}
	
	
	/**
	 * Creates the geometry of a Tower
	 */
	private static Node createTower(Tower tower) {
		Node geometry = new Node();
		
		float height = 0.0f;
		for(TowerElement element : tower.getElements()) {
			
			GuiTowerElement guiTowerElement = new GuiTowerElement(element);
			geometry.attachChild(guiTowerElement.get3D(0.0f, height, 0.0f));
			
			height += GuiTowerElement.getHeight(element);
		}
		geometry.setLocalScale(0.08f);
		geometry.rotate(0.0f, -45.0f * DEG2RAD, 0.0f);
		return geometry;
	}
	
	
	/**
	 * Attach all needed geometry and register all mouseOver and click events.
	 */
	public static void show() {
		GUI.getRootNode().attachChild(scenery);
		GUI.registerMouseOverable(mouseOverables);
		GUI.registerClickable(clickables);
	}
	
	
	/**
	 * Detach all geometry and unregister all mouseOver and click events.
	 */
	public static void hide() {
		GUI.getRootNode().detachChild(scenery);
		GUI.unregisterMouseOverable(mouseOverables);
		GUI.unregisterClickable(clickables);
	}
	
	
	/**
	 * Process click event: check if user clicked on an action location.
	 */
	public static void processMouseClick() {
		Vector2f cursor = GUI.getInputManager().getCursorPosition();
		int cursorX = (int)cursor.x;
		int cursorY = (int)cursor.y;
		
		Vector3f rayPoint = cam.getWorldCoordinates(new Vector2f(cursorX, cursorY), 0.0f);
		Vector3f rayPoint2 = cam.getWorldCoordinates(new Vector2f(cursorX, cursorY), 100.0f);
		Vector3f rayDirection = new Vector3f(rayPoint.x - rayPoint2.x, rayPoint.y - rayPoint2.y, rayPoint.z - rayPoint2.z);
		CollisionResults results = new CollisionResults();
        Ray ray = new Ray(rayPoint, rayDirection);
        clickables3D.collideWith(ray, results);
        
        if(results.size() > 0) {
        	String clickableName = results.getClosestCollision().getGeometry().getName();
        	if(clickableName.equals("PalaceOfTheCaliph")) {
        		GUI.changeState(GuiState.State.PALACE_OF_THE_CALIPH);
        	} else if(clickableName.equals("Bank")) {
        		GUI.changeState(GuiState.State.BANK);
        	} else if(clickableName.equals("ConstructionAuthority")) {
        		GUI.changeState(GuiState.State.CONSTRUCTION_AUTHORITY);
        	} else if(clickableName.equals("TowerElementMarket")) {
        		GUI.changeState(GuiState.State.TOWER_ELEMENT_MARKET);
        	} else if(clickableName.equals("HouseOfSpies")) {
        		GUI.changeState(GuiState.State.HOUSE_OF_SPIES);
        	}
        }
	}
	
	
	/**
	 * Start camera animation.
	 */
	public static void startCamAnimation(Location location, ICallback callback) {
		camAnimationStartLocation = cam.getLocation().clone();
		camAnimationStartLookAt = camLookAt.clone();
		if(location == Location.BASIC_SCENE_CAMERA) {
			camAnimationDestinationLocation = getLocationOf(Location.BASIC_SCENE_CAMERA);
			camAnimationDestinationLookAt = getLocationOf(Location.BASIC_SCENE_LOOK_AT);
		} else if(location == Location.PALACE_OF_THE_CALIPH || 
				  location == Location.BANK || 
				  location == Location.CONSTRUCTION_AUTHORITY || 
				  location == Location.TOWER_ELEMENT_MARKET || 
				  location == Location.HOUSE_OF_SPIES) {
			Vector3f position = getLocationOf(location);
			camAnimationDestinationLocation = new Vector3f(position.x - 50.0f, position.y + 20.0f, position.z + 50.0f);
			camAnimationDestinationLookAt = new Vector3f(position.x, position.y + 20.0f, position.z);
		} else {
			Vector3f position = getLocationOf(location);
			camAnimationDestinationLocation = new Vector3f(position.x - 23.0f, position.y + 12.0f, position.z + 37.0f);
			camAnimationDestinationLookAt = new Vector3f(position.x + 7.0f, position.y + 12.0f, position.z + 7.0f);
		}
		camAnimationTimer = 0.0f;
		camAnimationCallback = callback;
	}
	
	
	/**
	 * Sets a directional light used to light the scenery.
	 */
	public static void setLighting(Node rootNode, ViewPort viewPort, AssetManager assetManager) {
		Vector3f sunDirection = new Vector3f(0.3f, -0.7f, -0.2f).normalizeLocal();
		
		DirectionalLight sun = new DirectionalLight();
        sun.setDirection(sunDirection);
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun);
        
        PssmShadowRenderer pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 1);
        pssmRenderer.setDirection(sunDirection);
        viewPort.addProcessor(pssmRenderer);
	}
	
	
	/**
	 * Returns the coordinates of a specific Location.
	 */
	public static Vector3f getLocationOf(Location name) {
		if(name == Location.BASIC_SCENE_CAMERA) {
			return new Vector3f(16.0f, 24.0f, 496.0f);
			
		} else if(name == Location.BASIC_SCENE_LOOK_AT) {
			return new Vector3f(256.0f, 24.0f, 256.0f);
		
		} else if(name == Location.PALACE_OF_THE_CALIPH) {
			return new Vector3f(170.0f, terrain.getHeight(new Vector2f(180.0f, 337.0f)) + 5f, 347.0f);
			
		} else if(name == Location.BANK) {
			return new Vector3f(107.0f, terrain.getHeight(new Vector2f(107.0f, 325.0f)) + 4.5f, 325.0f);
		
		} else if(name == Location.CONSTRUCTION_AUTHORITY) {
			return new Vector3f(119.0f, terrain.getHeight(new Vector2f(119.0f, 358.0f)) + 4.5f, 358.0f);
		
		} else if(name == Location.TOWER_ELEMENT_MARKET) {
			return new Vector3f(144.0f, terrain.getHeight(new Vector2f(144.0f, 385.0f)) + 6.5f, 385.0f);
		
		} else if(name == Location.HOUSE_OF_SPIES) {
			return new Vector3f(191.0f, terrain.getHeight(new Vector2f(191.0f, 397.0f)) + 1.2f, 397.0f);
		
		} else if(name == Location.BUILDING_LOT_2_1) {
			return new Vector3f(buildingLotX[0][0], terrain.getHeight(new Vector2f(buildingLotX[0][0], buildingLotZ[0][0])), buildingLotZ[0][0]);
		
		} else if(name == Location.BUILDING_LOT_2_2) {
			return new Vector3f(buildingLotX[0][1], terrain.getHeight(new Vector2f(buildingLotX[0][1], buildingLotZ[0][1])), buildingLotZ[0][1]);
		
		} else if(name == Location.BUILDING_LOT_3_1) {
			return new Vector3f(buildingLotX[1][0], terrain.getHeight(new Vector2f(buildingLotX[1][0], buildingLotZ[1][0])), buildingLotZ[1][0]);
		
		} else if(name == Location.BUILDING_LOT_3_2) {
			return new Vector3f(buildingLotX[1][1], terrain.getHeight(new Vector2f(buildingLotX[1][1], buildingLotZ[1][1])), buildingLotZ[1][1]);
		
		} else if(name == Location.BUILDING_LOT_3_3) {
			return new Vector3f(buildingLotX[1][2], terrain.getHeight(new Vector2f(buildingLotX[1][2], buildingLotZ[1][2])), buildingLotZ[1][2]);
		
		} else if(name == Location.BUILDING_LOT_4_1) {
			return new Vector3f(buildingLotX[2][0], terrain.getHeight(new Vector2f(buildingLotX[2][0], buildingLotZ[2][0])), buildingLotZ[2][0]);
		
		} else if(name == Location.BUILDING_LOT_4_2) {
			return new Vector3f(buildingLotX[2][1], terrain.getHeight(new Vector2f(buildingLotX[2][1], buildingLotZ[2][1])), buildingLotZ[2][1]);
		
		} else if(name == Location.BUILDING_LOT_4_3) {
			return new Vector3f(buildingLotX[2][2], terrain.getHeight(new Vector2f(buildingLotX[2][2], buildingLotZ[2][2])), buildingLotZ[2][2]);
		
		} else if(name == Location.BUILDING_LOT_4_4) {
			return new Vector3f(buildingLotX[2][3], terrain.getHeight(new Vector2f(buildingLotX[2][3], buildingLotZ[2][3])), buildingLotZ[2][3]);
		
		}
		return null;
	}
	
	
	/**
	 * Sets a the camera used for the scenery.
	 */
	public static void setCamera(Camera cam, FlyByCamera freeCam) {
        Scenery.cam = cam;
        Scenery.freeCam = freeCam;
        
        // Change camera size so just to cover the space not covered by the right panel
        cam.resize(GUI.getWindowWidth() - GUI.getPanelWidth(), GUI.getWindowHeight(), true);
		cam.setLocation(getLocationOf(Location.BASIC_SCENE_CAMERA));
        setCamLookAt(getLocationOf(Location.BASIC_SCENE_LOOK_AT));
        cam.setFrustumFar(5000.0f);
        cam.update();
        
	}
	
	
	/**
	 * Set look-at point of the camera.
	 */
	private static void setCamLookAt(Vector3f lookAt) {
		cam.lookAt(lookAt, new Vector3f(0.0f, 1.0f, 0.0f));
		camLookAt = lookAt;
	}
	
	
	/**
	 * Return background color of the scene of the scenery.
	 */
	public static ColorRGBA getBackgroundColor() {
		return new ColorRGBA(0.4f, 0.6f, 1.0f, 1.0f);
	}
	
	
	/**
	 * Creates a square in 3D space.
	 */
	public static Geometry createQuad(float width, float height, Material material, float textureX, float textureY, float textureWidth, float textureHeight, boolean center) {
	
		Vector2f[] textureCoords = new Vector2f[4];
        textureCoords[0] = new Vector2f(textureX,                (1.0f - (textureY + textureHeight)));
        textureCoords[1] = new Vector2f(textureX + textureWidth, (1.0f - (textureY + textureHeight)));
        textureCoords[2] = new Vector2f(textureX,                (1.0f - (textureY)));
        textureCoords[3] = new Vector2f(textureX + textureWidth, (1.0f - (textureY)));
        
		Mesh mesh = new Mesh();
		
		Vector3f[] vertices = new Vector3f[4];
        
		if(center) {
        	vertices[0] = new Vector3f(-(width / 2.0f), -(height / 2.0f), 0.0f);
        	vertices[1] = new Vector3f( (width / 2.0f), -(height / 2.0f), 0.0f);
        	vertices[2] = new Vector3f(-(width / 2.0f),  (height / 2.0f), 0.0f);
        	vertices[3] = new Vector3f( (width / 2.0f),  (height / 2.0f), 0.0f);
		} else {
        	vertices[0] = new Vector3f( 0.0f, height, 0.0f);
        	vertices[1] = new Vector3f(width, height, 0.0f);
        	vertices[2] = new Vector3f( 0.0f,   0.0f, 0.0f);
        	vertices[3] = new Vector3f(width,   0.0f, 0.0f);
		}
		
		Vector3f[] normals = new Vector3f[4];
        normals[0] = new Vector3f(0.0f, 0.0f, 1.0f);
        normals[1] = new Vector3f(0.0f, 0.0f, 1.0f);
        normals[2] = new Vector3f(0.0f, 0.0f, 1.0f);
        normals[3] = new Vector3f(0.0f, 0.0f, 1.0f);
		
		
        int[] indexes = {2, 0, 1, 1, 3, 2};
 
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
        mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
        mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoords));
        mesh.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes));
 
        Geometry geometry = new Geometry(null, mesh);
        
        geometry.setMaterial(material);
        geometry.updateModelBound();
        return geometry;
	}
	
	
	/**
	 * Turns the free camera on or off.
	 */
	public static void toggleFreeCam() {
		freeCamOn = !freeCamOn;
		if(freeCamOn) {
			freeCam.setEnabled(true);
		} else {
			freeCam.setEnabled(false);
		}
	}
	
	
	/**
	 * Updates all towers of all players in the scene.
	 */
	public static void updateTowers() {
		for(int i = 0; i < Game.board().getPlayerNum(); i++) {
			towerNodes[i].detachAllChildren();
			ArrayList<Tower> towers = Game.board().getPlayer(i).getTowers();
			for(int j = 0; j < towers.size(); j++) {
				Tower tower = towers.get(j);
		        Node towerGeometry = createTower(tower);
		        towerGeometry.setLocalTranslation(getTowerPosition(i, j));
		        towerGeometry.updateModelBound();
		        towerGeometry.setQueueBucket(Bucket.Transparent);
		        towerNodes[i].attachChild(towerGeometry);
			}
		}
	}
	
	
	/**
	 * Updates the towers of the player in the scene with a temporary list of towers.
	 */
	public static void previewMyTowers(ArrayList<Tower> towers) {
		int i = Game.board().getMyIndex();
		towerNodes[i].detachAllChildren();
		for(int j = 0; j < towers.size(); j++) {
			Tower tower = towers.get(j);
		    Node towerGeometry = createTower(tower);
		    towerGeometry.setLocalTranslation(getTowerPosition(i, j));
		    towerGeometry.updateModelBound();
		    towerGeometry.setQueueBucket(Bucket.Transparent);
		    towerNodes[i].attachChild(towerGeometry);
		}
	}
	
	
	/**
	 * Returns a 3D vector to the center of the base of a tower.
	 */
	public static Vector3f getTowerPosition(int playerIndex, int towerIndex) {
		return new Vector3f(buildingLotX[Game.board().getPlayerNum() - 2][playerIndex] + (4.5f * (float) Math.sin((towerRotation[towerIndex] + 45.0f) * DEG2RAD)) , 0.0f, buildingLotZ[Game.board().getPlayerNum() - 2][playerIndex] - (4.5f * (float) Math.cos((towerRotation[towerIndex] + 45.0f) * DEG2RAD)));
	}
	
	
	/**
	 * Returns the position in 3D space of an tower element.
	 */
	public static Vector3f getTowerElementPosition(int playerIndex, int towerIndex, Tower tower, int elementIndex) {
		Vector3f position = getTowerPosition(playerIndex, towerIndex);
		for(int i = 0; i < elementIndex; i++) {
			position.y += 0.08 * GuiTowerElement.getHeight(tower.getElements().get(i));
		}
		return position;
	}
	
	
	/**
	 * Returns a vector in 2D space from a point in 3D space.
	 */
	public static Vector3f get2dPosition(Vector3f scenePosition) {
		return cam.getScreenCoordinates(scenePosition);
	}
	
	
	public static void update(float delta) {
		if(camAnimationTimer >= 0.0f) {
			float animationLength = 0.6f;
			camAnimationTimer += delta;
			if(camAnimationTimer >= animationLength) {
				cam.setLocation(camAnimationDestinationLocation);
				setCamLookAt(camAnimationDestinationLookAt);
				camAnimationTimer = -100.0f;
				if(camAnimationCallback != null) {
					camAnimationCallback.run();
				}
			} else {
				float animationProgress = (float) ((-0.5 * Math.cos((camAnimationTimer / animationLength) * Math.PI)) + 0.5);
				float locationX = camAnimationStartLocation.x + (animationProgress * (camAnimationDestinationLocation.x - camAnimationStartLocation.x));
				float locationY = camAnimationStartLocation.y + (animationProgress * (camAnimationDestinationLocation.y - camAnimationStartLocation.y));
				float locationZ = camAnimationStartLocation.z + (animationProgress * (camAnimationDestinationLocation.z - camAnimationStartLocation.z));
				float lookAtX = camAnimationStartLookAt.x + (animationProgress * (camAnimationDestinationLookAt.x - camAnimationStartLookAt.x));
				float lookAtY = camAnimationStartLookAt.y + (animationProgress * (camAnimationDestinationLookAt.y - camAnimationStartLookAt.y));
				float lookAtZ = camAnimationStartLookAt.z + (animationProgress * (camAnimationDestinationLookAt.z - camAnimationStartLookAt.z));
				cam.setLocation(new Vector3f(locationX, locationY, locationZ));
				setCamLookAt(new Vector3f(lookAtX, lookAtY, lookAtZ));
			}
		}
	}
	
}
